package com.atguigu.service.impl;

import com.atguigu.cache.ShopCache;
import com.atguigu.constant.RedisConst;
import com.atguigu.entity.SkuImage;
import com.atguigu.entity.SkuInfo;
import com.atguigu.exception.SleepUtils;
import com.atguigu.service.SkuDetailService;
import com.atguigu.service.SkuImageService;
import com.atguigu.service.SkuInfoService;
import com.atguigu.service.SkuSalePropertyValueService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 由于热点访问页面，加redis
 */
@Service
public class SkuDetailServiceImpl implements SkuDetailService {
    @Autowired
    private SkuInfoService skuInfoService;
    @Autowired
    private SkuImageService skuImageService;
    @Autowired
    private SkuSalePropertyValueService skuSalePropertyValueService;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private  RedissonClient redissonClient;
    @Autowired
    private RBloomFilter<Long> skuBloomFilter;
    @Override
    //前端需要 sku基本信息以及图片信息，把放在SkuInfo里返回
    public SkuInfo getSkuInfo(Long skuId) {
        SkuInfo skuInfoFromDb = getSkuInfoFromDb(skuId);
        return skuInfoFromDb;
        //return getSkuInfoFromRedis(skuId);
        //return getSkuInfoFromRedisWithThreadLocal(skuId);
        //return getSkuInfoFromRedisson(skuId);
    }
    // 利用 redisssion框架 来实现
     public  SkuInfo getSkuInfoFromRedisson(Long skuId) {
         //先从redis 里查数据库
         //定义一个格式key
         String cacheKey = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKUKEY_SUFFIX;
         SkuInfo skuInfoCache = (SkuInfo) redisTemplate.opsForValue().get(cacheKey);
         if (skuInfoCache == null) {
                 //弄一个细粒锁
                 String lockXi = "lock" + skuId;
                 //说明在缓存中没有数据,查数据库
                 RLock lock = redissonClient.getLock(lockXi);
                 lock.lock();
             try {
                 ////执行业务(缓存穿透)
                 //SkuInfo skuInfoFromDb = getSkuInfoFromDb(skuId);
                 ////防止缓存穿透（利用一个不存在的key,绕过缓存，打击数据库）
                 //if (skuInfoFromDb == null) {
                 //    SkuInfo emptySkuInfo = new SkuInfo();
                 //    redisTemplate.opsForValue().set(cacheKey, emptySkuInfo, RedisConst.SKUKEY_TIMEOUT, TimeUnit.SECONDS);
                 //}

                 //执行业务(随机缓存穿透)
                 //先去 布隆过滤器中查
                 boolean contains = skuBloomFilter.contains(skuId);
                 SkuInfo skuInfoFromDb=new SkuInfo();
                 if(contains){
                     //说明数据库里有,去查询数据库
                    skuInfoFromDb = getSkuInfoFromDb(skuId);
                 }
                 redisTemplate.opsForValue().set(cacheKey, skuInfoFromDb, RedisConst.SKUKEY_TIMEOUT, TimeUnit.SECONDS);
                 return skuInfoFromDb;
             } finally {
                 lock.unlock();
             }
         }
         return skuInfoCache;
     }
    // 利用 redis+lua+threadLocal 查询商品基本信息
    public ThreadLocal<String> threadLocal=new ThreadLocal<>();
    public  SkuInfo getSkuInfoFromRedisWithThreadLocal(Long skuId) {
        //查看redis是否有该数据
        String redisKey= RedisConst.SKUKEY_PREFIX+skuId+RedisConst.SKUKEY_SUFFIX;
        SkuInfo skuInfoCash = (SkuInfo) redisTemplate.opsForValue().get(redisKey);
        //定义锁的粗细
        String lockXi="lock-"+skuId;
        if(skuInfoCash==null){
            //还有很多操作要执行 去查缓存 去判断 去过滤等操作 200行代码要执行
            String token = threadLocal.get();
            boolean accquireLock=false;
            //代表第一次来 不参与自旋
            if(token==null){
                token= UUID.randomUUID().toString();
                accquireLock = redisTemplate.opsForValue().setIfAbsent(lockXi, token,RedisConst.SKUKEY_TIMEOUT, TimeUnit.SECONDS);
            }else{
                //代表已经拿到锁了
                accquireLock=true;
            }
            if(accquireLock){
                SleepUtils.sleep(3);
                SkuInfo skuInfoDb = doBusiness(skuId, redisKey);

                String luaScript="if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
                redisScript.setScriptText(luaScript);
                redisScript.setResultType(Long.class);
                redisTemplate.execute(redisScript, Arrays.asList(lockXi),token);
                //擦屁股 防止内存泄漏
                threadLocal.remove();
                return skuInfoDb;
            }else{
                //自旋 只做一件事情 拿锁
                for (;;){
                    SleepUtils.millis(50);
                    //重试拿锁
                    boolean retryAccquireLock = redisTemplate.opsForValue().setIfAbsent(lockXi, token,RedisConst.SKUKEY_TIMEOUT, TimeUnit.SECONDS);
                    if(retryAccquireLock){
                        //拿到锁以后就不要去自旋了
                        threadLocal.set(token);
                        break;
                    }
                }
               return getSkuInfoFromRedisWithThreadLocal(skuId);
            }
        }
        return skuInfoCash;
    }
  /*  // 利用 redisssion框架 来实现
    public  SkuInfo getSkuInfoFromRedisson(Long skuId) {
        //查看redis 里是否有数据
        String cacheKey=RedisConst.SKUKEY_PREFIX+skuId+RedisConst.SKUKEY_SUFFIX;
        SkuInfo skuInfoCache = (SkuInfo) redisTemplate.opsForValue().get(cacheKey);
        //定义细锁名
        if(skuInfoCache==null){
            String lockKey="lock"+skuId;
            RLock lock =redissonClient.getLock(lockKey);
            lock.lock();
            try {
                //说明redis 没有数据,要从数据库里查,执行业务
                SkuInfo skuInfoDb = getSkuInfoFromDb(skuId);
                if(skuInfoDb==null){
                    SkuInfo emptySkuInfo = new SkuInfo();
                    //数据放入缓存
                    redisTemplate.opsForValue().set(cacheKey,emptySkuInfo,RedisConst.SKUKEY_TIMEOUT,TimeUnit.SECONDS);

                }
                //把 数据放入到缓存里
                redisTemplate.opsForValue().set(cacheKey,skuInfoDb,RedisConst.SKUKEY_TIMEOUT,TimeUnit.SECONDS);
                return skuInfoDb;

            } finally {
                lock.unlock();
            }
        }
        return skuInfoCache;
    }*/
    private SkuInfo doBusiness(Long skuId, String lockKey) {
        //说明redis里没有该数据,所以该查询数据库
        SkuInfo skuInfo = getSkuInfoFromDb(skuId);
        //存入 redis里
        redisTemplate.opsForValue().set(lockKey, skuInfo, 10, TimeUnit.SECONDS);
        return skuInfo;
    }
    //从数据库中查询数据
    private SkuInfo getSkuInfoFromDb(Long skuId) {
        //a 查询商品的基本信息
        SkuInfo skuInfo = skuInfoService.getById(skuId);
        //b 查询商品的图片信息
        if(skuInfo!=null) {
            QueryWrapper<SkuImage> wrapper = new QueryWrapper<>();
            wrapper.eq("sku_id", skuId);
            List<SkuImage> imageList = skuImageService.list(wrapper);
            skuInfo.setSkuImageList(imageList);
        }
        return skuInfo;
    }
    // 从redis里查询数据
    private SkuInfo getSkuInfoFromRedis(Long skuId) {
        //查看redis是否有该数据
        String redisKey= RedisConst.SKUKEY_PREFIX+skuId+RedisConst.SKUKEY_SUFFIX;
        SkuInfo skuInfoCash = (SkuInfo) redisTemplate.opsForValue().get(redisKey);
        if(skuInfoCash==null){
            //说明redis里没有该数据,所以该查询数据库
            SkuInfo skuInfo = doBusiness(skuId, redisKey);
            return skuInfo;
        }
        return skuInfoCash;
    }


}
